﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SmartRoute.Data;
using SmartRoute.Randomness;

namespace SmartRoute.Analytics

{
    [Serializable]
    //contains a constructor to initialize a genepool and genetic operators, such as mutation and repopulation
    class GenePool
    {
        //list of the potential stops
        List<Stop> potentialStops;

        //number of stops
        int numberStops;

        public int NumberStops
        {
            get { return numberStops; }
            set { numberStops = value; }
        }

        //maximum number of buses
        int numberBuses;

        //chance that stop order will change for a chromosome
        int stopPermutationMutateChance;

        //chance that a stop ID will change
        int idMutateChance;

        int crossoverChance;

        //population size
        int popSize;

        public int PopSize
        {
            get { return popSize; }
            set { popSize = value; }
        }

        //School and depot for population
        Stop school;
        Stop depot;

        //current population
        List<Chromosome> population;

        internal List<Chromosome> Population
        {
            get { return population; }
            set { population = value; }
        }

        //the champion from the last round
        Chromosome champion;

        internal Chromosome Champion
        {
            get { return champion; }
            set { champion = value; }
        }

        //the champions score
        double bestScore;

        public double BestScore
        {
            get { return bestScore; }
            set { bestScore = value; }
        }

        //source of randomness
        Randomness.RandomSource rand;

        public Randomness.RandomSource Rand
        {
            get { return rand; }
            set { rand = value; }
        }

        //Fitness test for this population
        FitnessTest currentTest;

        double[] fitnessScores;

        public FitnessTest CurrentTest
        {
            get { return currentTest; }
            set { currentTest = value; }
        }

        //source of routing information
        IRoutingService routingData;

        //indicated wether there will be a set number of buses
        bool fixedNumberBuses;

        //CONSTRUCTOR for entire GenePool
        public GenePool(List<Stop> potentialStops, int numStops, int numBuses, bool fixedBuses, DynamicRouteCache routingData, int idMutateChance, int permutationMutateChance, int popSize,
                        Stop school, Stop depot, int passengerLimit, TimeSpan timeLimit, int crossoverchance)
        {
            this.potentialStops = potentialStops;
            this.numberStops = numStops;
            this.numberBuses = numBuses;
            this.fixedNumberBuses = fixedBuses;
            this.routingData = routingData;
            this.idMutateChance = idMutateChance;
            this.stopPermutationMutateChance = permutationMutateChance;
            this.popSize = popSize;
            population = new List<Chromosome>();
            this.school = school;
            this.depot = depot;
            this.bestScore = 0;
            this.crossoverChance = crossoverchance;
            rand = new RandomSource((new System.Random()).Next());
            currentTest = new FitnessTest(passengerLimit, timeLimit);
            fitnessScores = new double[popSize];
            this.initializePool();
            
        }

        //initializes the pool with new chromosomes
        public void initializePool()
        {
            for (int i = 0; i < popSize; i++)
            {
                Chromosome next = new Chromosome(this.potentialStops, this.depot, this.school, this.rand, this.fixedNumberBuses, this.numberBuses);
                population.Add(next);
            }
            //gets the fitness scores
            this.testFitnessAll();
        }

        //runs forwards until it automatically stops
        public BusSolution runUntilAutoStop()
        {
            bool atMin = false;
            while (!atMin)
            {
                double firstScore = this.BestScore;
                for (int i = 0; i < 1000; i++)
                {
                    this.stepForwardGeneration();
                }
                double secondScore = this.BestScore;

                if ((firstScore - secondScore) >= 0)
                {
                    atMin = true;
                    this.testFitnessAll();
                }

            }
            return this.convertChromosomeToSolution(this.Champion);
        }

        //repopulates the population based on fitness scores
        public void stepForwardGeneration()
        {
            List<Chromosome> newPool = new List<Chromosome>();
            Chromosome copy = Chromosome.DeepCopy(this.Champion);
            
            
            IEnumerable<Chromosome> wheel = this.rand.getRoulleteWheel(this.fitnessScores, this.Population);
            Chromosome[] wheelConverted = wheel.Take((popSize*2)+2).ToArray();
            Chromosome A = wheelConverted[0];
            Chromosome B = wheelConverted[1];
            
            for (int i = 0; i < popSize; i++)
            {
                A = wheelConverted[i];
                B = wheelConverted[i + popSize];
                Chromosome C = this.crossover(A, B, this.crossoverChance, this.rand);
                newPool.Add(C);
            }

            newPool[0] =copy;
            this.Population = newPool;
            this.mutatePool();
            this.testFitnessAll();

            
            
        }

        //goes through pool and mutates permutation sotp order and bus id based on chance
        public void mutatePool()
        {
            //preserve champion
            Chromosome temp = Chromosome.DeepCopy(this.Champion);
            
            foreach (Chromosome current in population)
            {
                current.mutateId(rand, idMutateChance);
                current.mutatePermutation(rand, stopPermutationMutateChance);
            }
            this.Champion = Chromosome.DeepCopy(temp);
            population[0] =this.Champion;
        }

        //tests the fitness scores for entire pop and returns as an array of doubles
        public double[] testFitnessAll()
        {
            double[] scores = new double[popSize];
            //double temp = -1;
            int index = 0;
            this.bestScore = 0;
            foreach(Chromosome current in population)
            {
                scores[index] = currentTest.testFitness(this.convertChromosomeToSolution(current));
                if (scores[index] >= bestScore)
                {
                    this.champion = Chromosome.DeepCopy(current);
                    this.bestScore = scores[index];
                }
                index++;
            }
            this.fitnessScores = scores;
            this.population[0] = Chromosome.DeepCopy(this.Champion);
            //test
            BusSolution solution = convertChromosomeToSolution(this.population[0]);
            double testCheck = CurrentTest.testFitness(solution);
            //endTest
            return scores;
        }

        public Chromosome crossover(Chromosome A, Chromosome B, int crossoverChance, RandomSource rand)
        {
            int willcrossover = rand.fromTo(0, 99);
            int crossoverLocation = -1;

            if (willcrossover > crossoverChance)
            {
                crossoverLocation = rand.fromTo(0, A.Stops.Count - 1);
                Chromosome temp = Chromosome.DeepCopy(A);
                for (int j = crossoverLocation; j < temp.Stops.Count; j++)
                {
                    temp.BusId[j] = B.BusId[j];
                }

                return temp;
            }

            return Chromosome.DeepCopy(A);
        }

        //converts the chromosome to a complete busSolution that contains routes and passengers on each bus
        public BusSolution convertChromosomeToSolution(Chromosome toDecode)
        {
            //Method returns decoded Bus Solution
            BusSolution decodedSolution = new BusSolution();

            //List of concatenated routes, one for each bus
            List<Route> concatenatedRoutes = new List<Route>();

            //List indicating number of passengers on each bus
            List<int> passengersOnEachBus = new List<int>();

            //indicates the time each bus goes before picking up first passenger
            List<TimeSpan> firstLegNoPassengers = new List<TimeSpan>();

            //loops through the number of buses to reconstruct the route
            for (int i = 0; i < numberBuses; i++)
            {

                //boolean indicates if the first leg was calculated
                bool calculatedFirstLeg = false;

                //initial timespan of 0 for the first leg, will stay the same if bus is not used
                TimeSpan firstLeg = new TimeSpan(0, 0, 0);

                //initial route returned for each bus, idles in place at the depot
                Route beginning = Route.IdleInPlace(toDecode.Stops[0], TimeSpan.Zero, routingData);
                Route nextLeg = Route.IdleInPlace(toDecode.Stops[0], TimeSpan.Zero, routingData);

                //starting at the depot
                Stop lastStopProccessed = toDecode.Depot;
                Stop nextStop;

                int passengers = 0;

                //loops through the permutation of stops and totals the distances and passengers for a specific bus ID
                for (int j = 0; j < numberStops; j++)
                {
                    //checks to see if the current bus goes to any of the stops
                    if (toDecode.BusId[j] == i)
                    {
                        //sets the next stop the bus will goto
                        nextStop = Stop.DeepCopy(toDecode.Stops[j]);
                        //calculates the route using the last stop visited
                        nextLeg = routingData.FromTo(lastStopProccessed, nextStop);
                        //sets the current stop to be the last stop visited
                        lastStopProccessed = nextStop;
                        //concatenates the route
                        beginning = Route.Concatenate(beginning, nextLeg);
                        //increments the passengers for that bus
                        passengers += nextStop.Passengers;
                        //sets the first lef without passengers if it has not been set yet
                        if (calculatedFirstLeg == false)
                        {
                            calculatedFirstLeg = true;
                            firstLeg = nextLeg.Duration;
                        }
                    }


                }
                //bus returns to school
                nextStop = toDecode.School;
                nextLeg = routingData.FromTo(lastStopProccessed, nextStop);
                Route.Concatenate(beginning, nextLeg);

                //add the current bus information
                concatenatedRoutes.Add(beginning);
                passengersOnEachBus.Add(passengers);
                firstLegNoPassengers.Add(firstLeg);

            }

            //add bus information to solution object
            decodedSolution.Routes = concatenatedRoutes;
            decodedSolution.Passengers = passengersOnEachBus;
            decodedSolution.FirstLegWithoutPassengersDistance = firstLegNoPassengers;


            //return solution
            return decodedSolution;
        }


    }
    
}
